28

昨天我写到“所有Javascript函数都是闭包”,有些同学表示还是接受不能。我好好的一个函数,怎么就成闭包了?那么,让我们来探究一下,Chrome(V8)到底是怎样实现闭包的。

从闭包到[[Scopes]]

现在按下F12,打开console,让我们随便找一个实验对象:

function simpleFunc() { }
// <- undefined

超简单超正常的函数吧,我们来验证一下:

simpleFunc
// <- ƒ simpleFunc() { }

说了超正常的,哪里闭包了?现在试试这个:

console.dir(simpleFunc)
// ƒ simpleFunc()
//   arguments: null
//   caller: null
//   length: 0
//   name: "simpleFunc"
//   prototype: {constructor: ƒ}
//   __proto__: ƒ ()
//   [[FunctionLocation]]: VM000:1
//   [[Scopes]]: Scopes[1]

咦,[[Scopes]]是什么?打开一看:

//   [[Scopes]]: Scopes[1]
//     0: Global {type: "global", name: "", object: Window}

这就是闭包的实现。东西都存在这里了。看起来simpleFunc只不过是纯洁的函数,但它实际上是(空的)自身代码+全局变量环境。换句话说,它正是“函数和声明该函数的词法环境的组合”。

再来个稍微复杂点的例子:

{
  let localVar = 1;
  function dirtyFunc() { return localVar++ }
}
// <- ƒ dirtyFunc() { return localVar++ }
console.dir(dirtyFunc)
// ƒ dirtyFunc()
//   [[Scopes]]: Scopes[2]
//     0: Block
//       localVar: 1
//     1: Global {type: "global", name: "", object: Window}

看,localVar存在这里了吧!大家老说什么“保持运行的数据状态”云云,其实都在[[Scopes]]里。dirtyFunc看起来是个普通的函数,但[[Scopes]]里却混了些东西。

所以,如果我们说人话,闭包实际上就是——

函数的代码+[[Scopes]]

超级好理解了吧。

宁愿用this也不用闭包

接下来让我们对闭包做些更深入的解析,然后就知道为什么大家宁愿用this也不用闭包了。

[[Scopes]]能用代码访问/复制/修改吗?

不能。想不靠console,找到副作用在哪儿?不行。想深拷贝目前状态?不行。想历史回放?不行。debug?自己慢慢琢磨去吧!

闭包会把所有东西都存下来吗?

{
  let localVar = 1;
  let unusedVar = 2;
  function dirtyFunc2() { return localVar++ }
}
console.dir(dirtyFunc2)
// ƒ dirtyFunc()
//   [[Scopes]]: Scopes[2]
//     0: Block
//       localVar: 1
//     1: Global {type: "global", name: "", object: Window}

至少Chrome是不会把所有东西都塞到闭包里的。

那闭包对垃圾回收没害处?

{
  let localVar = new Uint8Array(1000000000)
  function dirtyFunc3() { return localVar }
  function cleanFunc() { }
}
var dirtyFunc3 = null
console.dir(cleanFunc)
// ƒ cleanFunc()
//   [[Scopes]]: Scopes[2]
//     0: Block
//       localVar: Uint8Array(1000000000) [0, 0, …]
//     1: Global {type: "global", name: "", object: Window}

dirtyFunc3cleanFunc共享同一个[[Scopes]]项,但这个[[Scopes]]项并不会因为dirtyFunc3被回收而动态更新!所以无辜的cleanFunc就只好一直带着这1GB的垃圾,内存泄漏妥妥的。作为强迫症,这是我讨厌闭包最重要的原因。

真的所有Javascript函数都是闭包吗?

console.dir(alert)
// ƒ dirtyFunc()
//   [[Scopes]]: Scopes[0]
//     No properties

抱歉,我可能是不太严谨。很明显,浏览器自带的原生API函数都是在【里世界】声明的,所以没有词法环境,自然[[Scopes]]是空的。它们不是闭包。

最佳实践

宁愿用this也不用闭包。原因详见我的上一篇文章(从过程式到函数式)。

我的相关文章

Javascript闭包:从过程式到函数式

以上所有代码按Mozilla Public License, v. 2.0授权。
以上所有文字内容按CC BY-NC-ND 4.0授权。


liqi0816
1.9k 声望578 粉丝